#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <sys/types.h>

#include "stencil.hpp"
#include "definitions.h"
#include "Wrapper.hpp"


ostream &operator<<(ostream &os, Runnable &r) { 
  r.print(os);
  return os;
}


#include "base_uday_wrapper.hpp"
#include "check.hpp"
#ifdef _PAPI_
#include "papi_hook.hpp"
#endif

extern void stencil(double ***M, int pT, int pN, bool verbose, int num_threads);
extern "C" {
extern void heat_3d(double M[][2][Npadded][Npadded][Npadded], int pT, int pN);
}

int count_iter = 0;
int nb_samples = 0;
char const * function_name = "heat_3d_enlarged";

extern int PARAMS[16];

// The data
int Titer = 0, Niter = 0;

#ifdef HUGE_TLB
double (* __attribute__ ((aligned(16))) A)[2][Npadded][Npadded][Npadded];
double (* __attribute__ ((aligned(16)))Acheck)[2][Npadded][Npadded][Npadded];
#else
double A[1][2][Npadded][Npadded][Npadded] __attribute__ ((aligned(16)));
double Acheck[1][2][Npadded][Npadded][Npadded] __attribute__ ((aligned(16)));
#endif

//
//  Test whether a and b are equal (where epsilon is the error).
//  I.e.,
//
//  |a-b| <= epsilon |a|   or |a-b| <= epsilon |b|
//

// Checks the data 
int check() {
  int i;

  std::cout << "START CHECKING RESULTS" << std::endl;

  heat_3d(Acheck, Titer, Niter);

  for (i=0; i<Niter; i++) {
    if (check_matrices("A0", "A0check", 
		       &A[0][0][i][0][0], 
		       &Acheck[0][0][i][0][0], 
		       Niter, Npadded))
      return 1;
    if (check_matrices("A1", "A1check", 
		       &A[0][1][i][0][0], 
		       &Acheck[0][1][i][0][0], 
		       Niter, Npadded))
      return 1;
  }

  return 0;
}


// Initializing the data
void initialize_once() {
  int i, j, k;

  for (i = 0; i < Npadded; i++) {
    for (j = 0; j < Npadded; j++) {
      for (k = 0; k < Npadded; k++) {
	A[0][0][i][j][k] = (double)(rand() / ((double)RAND_MAX + 1));
	Acheck[0][0][i][j][k] = A[0][0][i][j][k];
	A[0][1][i][j][k] = (double)(rand() / ((double)RAND_MAX + 1));
	Acheck[0][1][i][j][k] = A[0][1][i][j][k];
      }
    }
  }
}







int main (int argc, char *argv[])
{
  bool verbose = false, perform_check = false, papi = false;
    int n;
    int i;
    int b;
    char *fname;
    int argi;
    int num_threads = -1;
    int num_trials = 1;

#ifdef HUGE_TLB
    A = (double (*)[Npadded][Npadded][Npadded]) malloc (2 * Npadded * Npadded * Npadded * sizeof(double));
    Acheck = (double (*)[Npadded][Npadded][Npadded]) malloc (2 * Npadded * Npadded * Npadded * sizeof(double));
#endif

    // Command line: stencil [-v] [-c] Titer Niter [number_of_threads]
    if (argc < 3 || argc > 7) {
        fprintf(stderr, "Incorrect number of arguments\n");
        fprintf(stderr, "Usage: stencil [-v] [-c] Titer Niter [number_of_threads]\n");
        return -1;
    }
    argi = 1;
    if (strcmp("-v", argv[argi]) == 0) {
        verbose = true;
        argi++;
    }
    if (strcmp("-c", argv[argi]) == 0) {
        perform_check = true;
        argi++;
    }
    if (strcmp("-T", argv[argi]) == 0) {
      argi++;
      num_trials = atol(argv[argi++]);
      std::cout << num_trials << " trials\n";
    }
    if (strcmp("-P", argv[argi]) == 0) {
      argi++;
      papi = true;
      std::cout << "Use PAPI\n";
    }


    shared_ptr<Runnable> s_tmp;
    int code_version = 0;
    if (strcmp("-V", argv[argi]) == 0) {
      argi++;
      code_version = atol(argv[argi++]);
    }

    Titer = atol(argv[argi++]);
    Niter = atol(argv[argi++]);
    if (argi < argc) {
        num_threads = atol(argv[argi]);
        if (num_threads <= 0 ) {
            fprintf(stderr, "error specifying number of threads\n");
            return -1;
        }
    }


    if (code_version == 0) {
      Stencil<4, TILETAGDIMMACRO, TASKDIMMACRO> *st = 
        new Stencil<4, TILETAGDIMMACRO, TASKDIMMACRO>(A, Titer, Niter, Niter, Niter);
      s_tmp = shared_ptr<Stencil<4, TILETAGDIMMACRO, TASKDIMMACRO> >(st);
    } else {
      Uday *ud = new Uday(A, Titer, Niter, Niter, Niter, code_version);
      s_tmp = shared_ptr<Uday>(ud);
    }    

    Runnable &s = *s_tmp;
    std::cout << "Running: " << s << std::endl;


    initialize_once();
    
#ifdef _PAPI_
    PAPI<Runnable> p;
    if (papi) {
      p.papi_loop(s);
    } else {
      s();
    }
#else
    for (int t=0; t<num_trials; t++) {
      s();
    }
#endif

    std::cout << "DONE\n";

    if (perform_check) {
      check();
    }

#ifdef HUGE_TLB
    free(A);
    free(Acheck);
#endif

    return 0;
}
